/**
 * @title			Tester Module
 * 
 * @author			alogwe
 * 
 * @version			v0.1
 * 
 * @description		Module provides functions for running tests.
 * 
 * @exports			add(Function, Array, Function)
 * 					clear()
 * 					run()
 * 					showLastResults()
 * 					setVerbose(Boolean)
 * 
 * @todo 			exports.log(String) but first requires core 'File' API 
 */

module.id = 'tester';


/**
 * Adds all information for a test.  All tests that have been added
 * can be performed by calling the exported run() function.
 * 
 * @param 	fTest		The Function object to be tested
 * 
 * @param 	aArgs		An Array object of arguments that will be passed to the test function
 * 
 * @param 	fAssert		The Function object that will be used as an 
 * 						assertion to determine whether the test passed or failed. 
 * 						The test is only considered successful if this function
 * 						returns a Boolean value of true.
 *
 * @see 	run();
 * 
 * @todo 	Make sure argument verification is correct
 */
exports.add = function(fTest, aArgs, fAssert) {
	if(!(fTest instanceof Function)){
		throw new Error("First argument must be a Function object. (The test function)");
	}
	if(!(aArgs instanceof Array)){
		throw new Error("Second argument must be an Array object. (Array of arguments)");
	}
	if(!(fAssert instanceof Function)){
		throw new Error("Third argument must be a Function object. (The assertion function)");
	}
	testfunctions.push(fTest);
	testargs.push(aArgs);
	testassertions.push(fAssert);
};


/**
 * Clear all test data
 */
exports.clear = function() {
	testfunctions = [];
	testargs = [];
	testassertions = [];
	testresults = [];
	pv("Cleared all test data.");
};


/**
 * Logs all test data to a file
 * @param 	sFilename	Filename to write test data, if file doesn't exist
 * 						it will be created.  If it does exist, it will be
 * 						written to in APPEND mode.
 * @todo	Write this after JSFile host object is completed and ready;
 */
// exports.log = function(sFilename) {
	// // TODO Write this;
// };

/**
 * Executes all tests that have been added with the exported add() function.
 * 
 * The result of the test function call is passed as the first 
 * argument to the assertion function for evaluation.
 * 
 * If an Error is caught while executing the test function, that
 * Error object is set as the return value for the test function call. 
 * This way assertion functions can be easily written to check 
 * for properly thrown Exceptions/Errors.
 * 
 * @see 	add();
 */
exports.run = function() {
	p("\n\t***** BEGIN TESTS *****\n");
	showVerbose();
	var numTests = testfunctions.length;
	p("Performing", numTests, "tests.");
	
	for(var i = 0, len = numTests; i < len; i++) {
		/*
		 * Execute function being tested 
		 * and save result.  If test function throws
		 * an error, that is saved as the result.
		 */
		var func = testfunctions[i];
		var args = testargs[i];
		var result;
		try {
			pv("\nTest#", i);
			pv("Calling function:", func.name);
			pv("with arguments array:", args.toSource());
			result = func.apply(this, args);
		} catch (err) {
			result = err;
		} finally {
			pv("Return value (toString):", result);
			
			// Can't call toSource() method on undefined;
			if (result != undefined) {
				pv("Return value (toSource):", result.toSource());	
			}
		}

		
		/*
		 * Execute the assertion function and pass it
		 * the result from the tested function above;
		 */
		var assertfunc = testassertions[i];
		var assertion = assertfunc(result);
		if(assertion === true) {
			pv("Test", i, "Passed.");
		} else {
			p("Test", i, "FAILED.\n");
		}
		
		/*
		 * Save results from calling the 
		 * assertion function;
		 */
		testresults[i] = assertion;
	}
	
	showResults();
	p("\n\t***** END TESTS *****");
};


/**
 * Shows the results of the tests
 */
exports.showLastResults = function() {
	showResults();
};


/**
 * Used to set verbose mode to true or false.
 * If verbose mode is true, more detailed output is provided
 * during each test.
 */
exports.verbose = function(val) {
	setVerbose(val);
};


var verbose = false;
var testfunctions = [];
var testargs = [];
var testassertions = [];
var testresults = [];


// Prints output regardless of verbose setting;
var p = Console.write;


// Only prints if in verbose mode;
function pv() {
	if(verbose === true) {
		var args = Array.prototype.slice.call(arguments);
		Console.write.apply(this, args);
	}
}


// Shows the results saved from last tests;
function showResults() {
	p("\n\t***** RESULTS *****\n");
	var numResults = testresults.length;
	var nPassed = 0;
	var nFailed = 0;
	
	for (var i=0, len=numResults; i<len; i++) {
		var msg;
		if (testresults[i] === true) {
			nPassed++;
			msg = "Test ["+ i +"]: Passed";
		} else {
			nFailed++;
			msg = "Test ["+ i +"]: \tFAILED!";
		}
		p(msg);
	}
	
	p("\n");
	p("Passed:", nPassed, "of", numResults, "(", ((nPassed/numResults)*100), "%)");
	p("Failed:", nFailed, "of", numResults, "(", ((nFailed/numResults)*100), "%)");
	p("\n\t***** END RESULTS *****");
};


// Sets verbose true or false;
function setVerbose(val) {
	if(val === true) {
		verbose = true;
	} else {
		verbose = false;
	}
	showVerbose();
};

// Prints a string showing the verbose setting's current state;
function showVerbose() {
	var mode;
	if(verbose === true) {
		p("Verbose mode is ENABLED.");
	} else {
		p("Verbose mode is DISABLED.");
		p("To enable verbose mode, use the exported verbose() function.");
	}
};






